package com.zf.bm.common.core.sys.web;

import com.alibaba.fastjson.JSONObject;
import com.google.code.kaptcha.Constants;
import com.zf.bm.common.constant.Constant;
import com.zf.bm.common.core.config.ConfigUtils;
import com.zf.bm.common.core.config.Global;
import com.zf.bm.common.core.excpetiom.SystemException;
import com.zf.bm.common.core.log.annotation.SysLog;
import com.zf.bm.common.core.shiro.config.ShiroConfig;
import com.zf.bm.common.core.shiro.realm.UserNamePasswordToken;
import com.zf.bm.common.core.shiro.utils.LogOutUtils;
import com.zf.bm.common.core.shiro.utils.ShiroUtils;
import com.zf.bm.common.core.sys.entity.Login;
import com.zf.bm.common.core.sys.entity.User;
import com.zf.bm.common.core.sys.service.PasswdRetryLimitService;
import com.zf.bm.common.core.sys.service.UserService;
import com.zf.bm.common.core.sys.utils.DictUtils;
import com.zf.bm.common.dto.ObjectConvert;
import com.zf.bm.common.core.sys.dto.LoginDTO;
import com.zf.bm.common.core.sys.dto.UserDTO;
import com.zf.bm.common.utils.CacheUtils;
import com.zf.bm.common.utils.SpringContextHolder;
import com.zf.bm.common.web.BaseController;
import com.zf.bm.modules.system.entity.MonitorAuditLog;
import com.zf.bm.modules.system.service.MonitorAuditLogService;
import com.zf.bm.modules.system.utils.SystemUtils;
import com.zf.common.enums.McEventTypeEnum;
import com.zf.common.enums.McOperateTypeEnum;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.util.Date;
import java.util.List;

import static com.zf.bm.common.constant.Constant.CenterAuditType.ENUM_USER_OPT;


/**
 * 登录Controller
 */
@RestController
public class LoginController extends BaseController {

    @Autowired
    private UserService userService;
    @Autowired
    private ShiroConfig config;
    @Autowired
    private PasswdRetryLimitService passwdRetryLimitService;

    //是否开启多因子登录
    @Value("${shiro.mul-auth}")
    private boolean mulAuth;
    @Value("${login.license.flag}")
    private String loginLicenseFlag;
    @Value("${login.license.path}")
    private String loginLicensePath;
    @Value("${sys.root}")
    private boolean rootAuth;


    private String gmCertRootPath = "/usr/local/nginx/conf/gm_certs/SM2RootX.der";
    private String gmCertPrivatePath = "/usr/local/nginx/conf/gm_certs/SM2Private.der";
    private String gmCertXPath = "/usr/local/nginx/conf/gm_certs/SM2X.der";
    private static final String VALIDATE_KEY_FAIL = "key验证失败";
    private static final String ACCOUNT_LOCKED_UNTIL = "该账号将被锁定至：";
    private static final String ACCOUNT_LOCKED = ",该账号被锁定";
    private static final String ACCOUNT_LOCKED_CONTACT_ADMIN = "账号被锁定，请联系管理员";



    /**
     * 用户登录
     *
     * @param login
     * @param response
     * @return
     */

    @SysLog("用户登录")
    @PostMapping("${adminPath}/login")
    public Result login(@RequestBody Login login, HttpServletRequest request, HttpServletResponse response) {
        MonitorAuditLogService monitorAuditLogService = SpringContextHolder.getBean(MonitorAuditLogService.class);
        try {
            if (rootAuth && "root".equalsIgnoreCase(login.getUserName())) {
                throw new AuthenticationException("root无效");
            }

            Result fail = validateCode(login);
            if (fail != null) {
                return fail;
            }

            UserUtils.userIpTable.put(login.getUserName(), IPUtils.getRealIpAddress(request));
            Subject subject = SecurityUtils.getSubject();
            UserNamePasswordToken userToken = new UserNamePasswordToken(login);
            subject.login(userToken);
            LoginDTO loginDTO = new LoginDTO();
            ObjectConvert<UserDTO> objectToDto = new ObjectConvert();
            UserDTO userDTO = new UserDTO();
            User account = UserUtils.getUser();
            userDTO = objectToDto.toDto(account, userDTO);
            //判断用户是否被冻结
            if (account != null) {
                account.setDelFlag("0");
            }
            User u = userService.getByLoginName(account);
            Date currentDate = new Date();
            Result userResult = validateLoginUser(monitorAuditLogService, account, u, currentDate);
            if (userResult != null) {
                return userResult;
            }
            Result keyResult = validateKey(login, monitorAuditLogService, u);
            if (keyResult != null) {
                return keyResult;
            }
            String token = ShiroUtils.createToken(login);
            loginDTO.setToken(token);
            userDTO.setIsGuide(userService.isGuide(userDTO.getUsername()));
            loginDTO.setUser(userDTO);
            response.setHeader(Constant.TOKEN, loginDTO.getToken());
            //更新用户登录新
            account.setLoginDate(new Date());
            account.setLoginIp(HttpUtils.getRemoteAddr());
            account.updateById();
            //用户登录成功清空密码错误次数
            CacheUtils.cleanPasswdRetryCount(u.getLoginName());
            SystemUtils.saveSystemLog(login.getUserName(), "登录", 1, "用户登录成功");
            //事件审计
            MonitorAuditLog monitorAuditLog = new MonitorAuditLog();
            monitorAuditLog.setOperateCommand("用户登录");
            monitorAuditLog.setEventType(ENUM_USER_OPT.getMsg());
            monitorAuditLog.setOperateDescr("用户登录成功");

            monitorAuditLogService.save(monitorAuditLog);

            //判断密码是否需要重置
            setModifyPwdMsg(u.getModifyPwd(), loginDTO, account);

            return new Result(loginDTO);

        } catch (SystemException e) {
            return new Result(ResultStatus.VERIFICATION_FAIL.getCode(), "验证码已失效，请刷新验证码");
        } catch (LockedAccountException e) {
            SystemUtils.saveSystemLog(login.getUserName(), "登录", 1, "用户账号被锁定");
            monitorAuditLogService.saveMonitorAuditLog(McOperateTypeEnum.USER_LOGIN.getValue()
                    , "用户账号被锁定", McEventTypeEnum.USER_OPERATE.getValue(), login.getUserName());
            return new Result(ResultStatus.PWD_LIMIT_FAIL.getCode(), "账号被锁定,请联系管理员");
        } catch (AuthenticationException e) {

            String message = "用户登录失败，用户名或密码错误";
            logger.info(message);
            int passwdRetries = CacheUtils.incPasswdRetryCount(login.getUserName());
            int remain = config.getPwdMaxRetries() - passwdRetries;
            if (passwdRetries >= config.getPwdMaxRetries() - 1) {
                passwdRetryLimitService.handle(login.getUserName(), remain);
            }
            String opEvebtDesc = "用户登录失败";
            Result lockResult = lockAccount(login, monitorAuditLogService, remain, opEvebtDesc);
            if (lockResult != null) {
                return lockResult;
            }

            String msg = "账号或密码输入错误" + config.getPwdMaxRetries() + "次账号将被锁定, 您还能再试" + remain + "次";
            SystemUtils.saveSystemLog(login.getUserName(), "登录", 1, opEvebtDesc + "," + msg);
            monitorAuditLogService.saveMonitorAuditLog(McOperateTypeEnum.USER_LOGIN.getValue()
                    , msg, McEventTypeEnum.USER_OPERATE.getValue(), login.getUserName());
            return new Result(ResultStatus.PWD_LIMIT_FAIL.getCode(), msg);
        }

    }

    /**
     * 添加修改密码提示语
     * @param modifyPwd
     * @param loginDTO
     * @param account
     */
    private void setModifyPwdMsg(Integer modifyPwd, LoginDTO loginDTO, User account) {
        if (ValidatorUtil.isEmptyObj(modifyPwd) || Constant.MODIFY_PWD_INIT.equals(modifyPwd)) {
            loginDTO.setModifyPwdMsg("首次登陆需要修改密码");
            return;
        }

        if (Constant.MODIFY_PWD_RESET.equals(modifyPwd)) {
            loginDTO.getUser().setModifyPwd(Constant.MODIFY_PWD_INIT);
            loginDTO.setModifyPwdMsg("重置用户密码需要修改密码");
            return;
        }

        Date lastUpdateDate = account.getUpdateTime();
        Date nowDate = new Date();
        Integer period = account.getPwdPeriod();
        //long b = a / (1000 * 60 * 60 * 24); root用户首次登录不用跳转修改密码页面
        if ((nowDate.getTime() - lastUpdateDate.getTime()) / (1000 * 60 * 60 * 24) >= period && !isRoot(loginDTO.getUser().getUsername())) {
            loginDTO.getUser().setModifyPwd(Constant.MODIFY_PWD_INIT);
            loginDTO.setModifyPwdMsg("密码已过期需要修改密码");
        }
    }

    private Result lockAccount(Login login, MonitorAuditLogService monitorAuditLogService, int remain, String opEvebtDesc) {
        if (remain <= 0) {
            Date frozenDate = passwdRetryLimitService.getFrozenDate(login.getUserName());
            if (frozenDate != null) {
                String dateStr = DateTimeUtils.formatDate(frozenDate, "HH:mm:ss");
                SystemUtils.saveSystemLog(login.getUserName(), "登录", 1, opEvebtDesc + ",该账号将被锁定至：" + dateStr);
                monitorAuditLogService.saveMonitorAuditLog(McOperateTypeEnum.USER_LOGIN.getValue()
                        , opEvebtDesc + ACCOUNT_LOCKED, McEventTypeEnum.USER_OPERATE.getValue(), login.getUserName());
                return new Result(ResultStatus.PWD_LIMIT_FAIL.getCode(), ACCOUNT_LOCKED_UNTIL + dateStr);
            }
            SystemUtils.saveSystemLog(login.getUserName(), "登录", 1, opEvebtDesc + ACCOUNT_LOCKED);
            monitorAuditLogService.saveMonitorAuditLog(McOperateTypeEnum.USER_LOGIN.getValue()
                    , opEvebtDesc + ACCOUNT_LOCKED, McEventTypeEnum.USER_OPERATE.getValue(), login.getUserName());
            return new Result(ResultStatus.PWD_LIMIT_FAIL.getCode(), "该账号被锁定");
        }
        return null;
    }

    private Result validateKey(Login login, MonitorAuditLogService monitorAuditLogService, User u) {
        if (!mulAuth) {
            return null;
        }
        //验证key
        if (null == login.getCert()) {
            SystemUtils.saveSystemLog(u.getLoginName(), "登录", 1, VALIDATE_KEY_FAIL);
            monitorAuditLogService.saveMonitorAuditLog(McOperateTypeEnum.USER_LOGIN.getValue()
                    , VALIDATE_KEY_FAIL, McEventTypeEnum.USER_OPERATE.getValue(), u.getLoginName());
            return new Result(ResultStatus.FAIL.getCode(), "key验证失败，请检查后再试");
        } else {
            JSONObject jsonObject = JSONObject.parseObject(login.getCert());
            String[] certCommonName = {"secadm", "auditadm", "pp2", "tst2", "12345", "abcde", "security", "audit", "admin",
                    "lyy2", "gyw2", "123-123", "pp-test"};
            int flag = 0;
            for (String str : certCommonName) {
                if (str.equals(jsonObject.get("cert_common_name"))) {
                    flag = 1;
                    break;
                }
            }
            if (flag == 0) {
                SystemUtils.saveSystemLog(u.getLoginName(), "登录", 1, VALIDATE_KEY_FAIL);
                monitorAuditLogService.saveMonitorAuditLog(McOperateTypeEnum.USER_LOGIN.getValue()
                        , VALIDATE_KEY_FAIL, McEventTypeEnum.USER_OPERATE.getValue(), u.getLoginName());
                return new Result(ResultStatus.FAIL.getCode(), "key验证失败，请检查后再试");
            }
        }
        //进行用户角色验证
        if (!login.getKeyRole().equals(userService.getRoleByLoginName(u.getLoginName()))) {
            SystemUtils.saveSystemLog(u.getLoginName(), "登录", 1, "用户角色验证失败");
            monitorAuditLogService.saveMonitorAuditLog(McOperateTypeEnum.USER_LOGIN.getValue()
                    , "用户角色验证失败", McEventTypeEnum.USER_OPERATE.getValue(), u.getLoginName());
            return new Result(ResultStatus.FAIL.getCode(), "用户角色验证失败，请检查后再试");
        }
        //证书验证
        if (!isRightGMCert()) {
            SystemUtils.saveSystemLog(u.getLoginName(), "登录", 1, "证书验证失败");
            monitorAuditLogService.saveMonitorAuditLog(McOperateTypeEnum.USER_LOGIN.getValue()
                    , "证书验证失败", McEventTypeEnum.USER_OPERATE.getValue(), u.getLoginName());
            return new Result(ResultStatus.FAIL.getCode(), "证书验证失败，请检查后再试");
        }
        //验证密文
        if (!isRightCryptograph(login.getCryptograph())) {
            SystemUtils.saveSystemLog(u.getLoginName(), "登录", 1, "密文验证失败");
            monitorAuditLogService.saveMonitorAuditLog(McOperateTypeEnum.USER_LOGIN.getValue()
                    , "密文验证失败", McEventTypeEnum.USER_OPERATE.getValue(), u.getLoginName());
            return new Result(ResultStatus.FAIL.getCode(), "密文验证失败，请检查后再试");
        }
        return null;
    }

    private Result validateLoginUser(MonitorAuditLogService monitorAuditLogService, User account, User u, Date currentDate) {
        //0用户不可登录
        if (!"0".equals(u.getLoginFlag())) {
            return null;
        }
        Date frozneDate = u.getFrozenDate();
        if (null != frozneDate && frozneDate.before(currentDate)) {
            //解锁用户
            userService.updateLoginFlag(u.getLoginName(), ConfigUtils.YES, null);
            if (account != null) {
                account.setLoginFlag(ConfigUtils.YES);
            }
        } else if (null != frozneDate) {
            String dateStr = DateTimeUtils.formatDate(frozneDate, "HH:mm:ss");
            SystemUtils.saveSystemLog(u.getLoginName(), "登录", 1, ACCOUNT_LOCKED_UNTIL + dateStr);
            monitorAuditLogService.saveMonitorAuditLog(McOperateTypeEnum.USER_LOGIN.getValue()
                    , ACCOUNT_LOCKED_UNTIL + dateStr, McEventTypeEnum.USER_OPERATE.getValue(), u.getLoginName());
            return new Result(ResultStatus.FAIL.getCode(), ACCOUNT_LOCKED_UNTIL + dateStr);
        } else {
            SystemUtils.saveSystemLog(u.getLoginName(), "登录", 1, ACCOUNT_LOCKED_CONTACT_ADMIN);
            monitorAuditLogService.saveMonitorAuditLog(McOperateTypeEnum.USER_LOGIN.getValue()
                    , ACCOUNT_LOCKED_CONTACT_ADMIN, McEventTypeEnum.USER_OPERATE.getValue(), u.getLoginName());
            return new Result(ResultStatus.FAIL.getCode(), ACCOUNT_LOCKED_CONTACT_ADMIN);
        }
        return null;
    }

    private Result validateCode(Login login) {
        if (config.isValidateCodeEnable() && !DictUtils.isAutoTest()) {
            if (StringUtil.isEmpty(login.getValidateCode())) {
                return new Result(ResultStatus.FAIL.getCode(), "请输入验证码");
            }
            String kaptcha = ShiroUtils.getValidateCode(Constants.KAPTCHA_SESSION_KEY);
            if (!login.getValidateCode().equalsIgnoreCase(kaptcha)) {
                return new Result(ResultStatus.FAIL.getCode(), "验证码错误");
            }
        }
        return null;
    }



    /**
     * 登录时获取license
     *
     * @return 获取提供的mac信息
     */
    public String getLicense() {
        String license = "";
        try (FileReader fq = new FileReader(Global.getConfig("login.license.path"));

             BufferedReader b = new BufferedReader(fq);) {

            String str;
            while ((str = b.readLine()) != null) {
                license = str;
            }
        } catch (Exception e) {
            logger.error("get license msg error ", e);
            return "";
        }
        return license;
    }

    private boolean isRoot(String name) {
        if (ValidatorUtil.isEmptyObj(name)) {
            return false;
        }

        // 是否root用户
        if ("root".equals(name)) {
            return true;
        }
        return false;
    }

    private boolean isRightGMCert() {
        String md5Root = "621d66436fc69d655607165b34f7d3c6";
        String md5Private = "c6a55c47f7a4eede5a529bfce7a8bce8";
        String md5X = "e0aa6d94fde795088acb5e447b669519";

        File gmCertRootFile = new File(gmCertRootPath);
        File gmCertPrivateFile = new File(gmCertPrivatePath);
        File gmCertXFile = new File(gmCertXPath);
        if (!gmCertRootFile.exists() || !gmCertPrivateFile.exists() || !gmCertXFile.exists()) {
            return false;
        }
        List<String> strs = RunTimeUtils.getResult("md5sum /usr/local/nginx/conf/gm_certs/SM2RootX.der /usr/local/nginx/conf/gm_certs/SM2Private.der /usr/local/nginx/conf/gm_certs/SM2X.der");
        logger.info(strs.toString());
        if (strs.size() < 3) {
            return false;
        }
        if (!strs.get(0).contains(md5Root)) {
            return false;
        }
        if (!strs.get(1).contains(md5Private)) {
            return false;
        }
        return strs.get(2).contains(md5X);
    }

    private boolean isRightCryptograph(String cryptograph) {
        if (cryptograph != null) {
            try {
                cryptograph = EncryptUtils.decrypt(cryptograph);
            } catch (Exception e) {
                logger.error("pwd解码失败 exception {}", e.getMessage());
            }
            logger.info("密文解密通过 " + cryptograph);
            return true;
        }
        return false;
    }

    /**
     * 退出登录
     *
     * @param
     * @param response
     * @return
     */

    @SysLog("退出登录")
    @PostMapping("${adminPath}/logout")
    public Result logout(HttpServletRequest request, HttpServletResponse response) {
        LogOutUtils.logout(request);
        return new Result(ResultStatus.SUCCESS.getCode(), "退出成功，请重新登录！");
    }

}
